class Asm6502 implements IAsm {
  class Register {
    static final int A = 1;// 8-bit accumulator
    static final int X = 2, Y = 3;// 8-bit index
    static final int P = 4;// 8-bit status
    static final int S = 5;// 8-bit stack hard-wired from 0x0100 to 0x01FF
    static final int PC = 6;// 16-bit program counter
    const Register(this._r);
    final int _r;
  };

  class Flag {
    static final int CARRY = 0;
    static final int ZERO= 1;
    static final int INTERRUPT= 2;
    static final int DECIMAL= 3;
    static final int BREAK= 4;
    static final int OVERFLOW= 6;
    static final int NEGATIVE= 7;
    const Flag(this._f);
    final int _f;
  }

  class AddressingMode: {
    static final AddressingMode ACCUMULATOR= 0;
    static final AddressingMode IMMEDIATE= 1;
    static final AddressingMode ZERO_PAGE= 2;
    static final AddressingMode ZERO_PAGE_INDEX= 3;
    static final AddressingMode ABSOLUTE= 4;
    static final AddressingMode ABSOLUTE_INDEX= 5;
    static final AddressingMode INDIRECT= 6;
    static final AddressingMode INDIRECT_X= 7;
    static final AddressingMode INDIRECT_Y= 8;
    const AddressingMode(this._mode);
    final int _mode;
  };

  List<Register> _r;
  List<Flag> _f;

  void initialize() {
    _r = new List<Register>(Register.PC+1);
    _r[Register.A] = 0;
    _r[Register.X] = _r[Register.Y] = 0;
    _r[Register.P] = 0;
    _r[Register.S] = 0;
    _r[Register.PC]= 0;

    _f = new List<Flag>(Flag.NEGATIVE+1);
    _f[Flag.CARRY] = 0;
    _f[Flag.ZERO] = 0;
    _f[Flag.INTERRUPT] = 0;
    _f[Flag.DECIMAL] = 0;
    _f[Flag.BREAK] = 0;
    _f[Flag.OVERFLOW] = 0;
    _f[Flag.NEGATIVE] = 0;
  }

  void updateRegisterView(register_view) {
    var table = document.getElementById('register_table');
    if (table === null) {
      table = document.createElement('table');
      table.id = 'register_table';
      register_view.appendChild(table);

      for (Register r in _r) {
        var row = document.createElement('tr');
        var key_cell = document.createElement('td');
        key_cell.innerHTML = r;
        var value_cell = document.createElement('td');
        value_cell.id = r + '.value';
        row.appendChild(key_cell);
        row.appendChild(value_cell);
        table.appendChild(row);
        if (r === Register.P) {
          var flags = document.createElement('table');
          for (Flag f in Flags) {
            var flag_row = document.createElement('tr');
            var flag_key = document.createElement('td');
            flag_key.innerHTML = f;
            var flag_value = document.createElement('td');
            flag_value.id = r + '.' + f + '.value';
            flag_row.appendChild(flag_key);
            flag_row.appendChild(flag_value);
            flags.appendChild(flag_row);
          }
          value_cell.appendChild(flags);
        }
      }
    }

    // TODO:  switch  between  hex  and  dec
    for (Register reg in _r) {
      var cell = document.getElementById(reg + '.value');
      if (reg === Register.S)
        cell.innerHTML = '0x01' + decToHex(_r[reg], 2);
      else if (reg === Register.P) {
        for (var fl in Flags) {
          cell = document.getElementById(reg + '.' + fl + '.value');
          cell.innerHTML = (getFlag(fl) === 1);
        }
      } else
        cell.innerHTML = '0x' + decToHex(_r[reg], 2);
    }
  }

  void setFlag(f, b) {
    if (_f[f] === undefined)
      throw new Error('Unknown flag');

    if (b)
      _r_.P |= (1 << _f[f]);
    else
      _r_.P &= ~(1 << _f[f]);
  }

  bool getFlag(f) {
    if (_f[f] === undefined)
      throw new Error('Unknown flag');

    return (_r.P & (1 << _f[f]));
  }

  // TODO: page crossing cycle counts.
  Dynamic getAddressingMode(operands) {
    // accumulator
    if (operands === 'A') {
      return { mode: AddressingMode.ACCUMULATOR,
        value: r_.A,
        cycles: 0 };
    }

    // immediate
    var match = operands.match(/^#\$([0-9A-Z]{2})$/);
    if (match !== null) {
      return { mode: AddressingMode.IMMEDIATE,
        value: parseInt(RegExp.$1, 16),
        cycles: 2 };
    }

    // absolute or zero page
    match = operands.match(/^\$([0-9A-Z]{1,4})$/);
    if (match !== null) {
      return { mode: (RegExp.$1.length <= 2) ? AddressingMode.ZERO_PAGE : AddressingMode.ABSOLUTE,
        address: parseInt(RegExp.$1, 16),
        cycles: RegExp.$1.length <= 2 ? 3 : 4 };
    }

    // absolute,X, absolute,Y, or zero page,X
    match = operands.match(/^\$([0-9A-Z]{1,4}),\s*(X|Y)$/);
    if (match !== null) {
      return { mode: (RegExp.$1.length <= 2) ? AddressingMode.ZERO_PAGE_INDEX : AddressingMode.ABSOLUTE_INDEX,
        address: parseInt(RegExp.$1, 16) + _r[RegExp.$2],
        cycles: 6 };
    }

    match = operands.match(/^\(\$([0-9A-Z]{1,4})\)$/);
    if (match !== null) {
      return { mode: AddressingMode.INDIRECT,
        address: parseInt(RegExp.$1, 16),
        cycles: 5 };
    }

    // (indirect,X)
    match = operands.match(/^\(\$([0-9A-Z]{1,4}),\s*X\)$/);
    if (match !== null) {
      return { mode: AddressingMode.INDIRECT_X,
        address: parseInt(RegExp.$1, 16) + r_.X,
        cycles: 6 };
    }

    // (indirect),Y
    match = operands.match(/^\(\$([0-9A-Z]{1,4})\),\s*Y$/);
    if (match !== null) {
      return { mode: AddressingMode.INDIRECT_Y,
        address: Memory.readWord(parseInt(RegExp.$1, 16)) + r_.Y,
        cycles: 5 };
    }

    throw 'Unable to parse addressing mode from \'' + operands + '\'';
  }

  // TODO:  Track  size  of  instructions  so  relative  branchs  work. 
  // TODO:  Pass  allowed  addressing  modes  to  getAddressingMode  to  minimize  regexpv
  Map _instruction_map = {
    // MEMORY
    // INSTRUCTIONS
    // Load
    // Accumulator
    'LDA': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      r_.A = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;
    },

    // Load
    // X
    'LDX': function(operands) {
      var addr_mode = getAddressingMode(operands);
      // TODO:
      // check
      // for
      // ZERO_PAGE_INDEX_X
      if (addr_mode === AddressingMode.INDEX_X || addr_mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      r_.X = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;
    },

    // Load
    // Y
    'LDY': function(operands) {
      var addr_mode = getAddressingMode(operands);
      // TODO:
      // check
      // for
      // ZERO_PAGE_INDEX_X
      if (addr_mode === AddressingMode.INDEX_X || addr_mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      r_.Y = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;
    },

    // Store
    // accumulator.
    'STA': function(operands) {
      var addr_mode = getAddressingMode(operands);
      if (addr_mode.mode === AddressingMode.IMMEDIATE)
        throw 'Invalid addressing mode';

      Memory.writeByte(addr_mode.address, r_.A);
      return addr_mode.cycles;
    },

    // Store
    // X
    'STX': function(operands) {
      var addr_mode = getAddressingMode(operands);
      if (addr_mode.mode !== AddressingMode.ZERO_PAGE &&
          addr_mode.mode !== AddressingMode.ZERO_PAGE_INDEX &&
          addr_mode.mode !== AddressingMode.ABSOLUTE) {
        throw 'Invalid addressing mode';
      }

      Memory.writeByte(addr_mode.address, r_.X);
      return addr_mode.cycles;
    },

    // Store
    // Y
    'STY': function(operands) {
      var addr_mode = getAddressingMode(operands);
      if (addr_mode.mode !== AddressingMode.ZERO_PAGE &&
          addr_mode.mode !== AddressingMode.ZERO_PAGE_INDEX &&
          addr_mode.mode !== AddressingMode.ABSOLUTE) {
        throw 'Invalid addressing mode';
      }

      Memory.writeByte(addr_mode.address, r_.Y);
      return addr_mode.cycles;
    },

    // REGISTER
    // INSTRUCTIONS
    // Transfer
    // accumulator
    // to
    // X
    'TAX': function(operands) {
      r_.X = r_.A;
      setFlag('ZERO', r_.X === 0);
      setFlag('NEGATIVE', (r_.X >> 7) === 1);
      return 2;
    },

    // Transfer
    // accumulator
    // to
    // Y
    'TAY': function(operands) {
      r_.Y = r_.A;
      setFlag('ZERO', r_.Y === 0);
      setFlag('NEGATIVE', (r_.Y >> 7) === 1);
      return 2;
    },

    // Transfer
    // X
    // to
    // accumulator
    'TXA': function(operands) {
      r_.A = r_.X;
      setFlag('ZERO', r_.A === 0);
      setFlag('NEGATIVE', (r_.A >> 7) === 1);
      return 2;
    },

    // Transfer
    // Y
    // to
    // accumulator
    'TYA': function(operands) {
      r_.A = r_.Y;
      setFlag('ZERO', r_.A === 0);
      setFlag('NEGATIVE', (r_.A >> 7) === 1);
      return 2;
    },

    // Transfer
    // stack
    // pointer
    // to
    // X
    'TSX': function(operands) {
      r_.X = r_.S;
      setFlag('ZERO', r_.X === 0);
      setFlag('NEGATIVE', (r_.X >> 7) === 1);
      return 2;
    },

    // Transfer
    // X
    // to
    // stack
    // pointer
    'TXS': function(operands) {
      r_.S = r_.X;
      return 2;
    },

    // STACK
    // INSTRUCTIONS
    // Push
    // accumulator
    'PHA': function(operands) {
      Memory.writeByte(0x0100 + r_.S, r_.A);
      ++r_.S;
      return 3;
    },

    // Push
    // status
    // register
    'PHP': function(operands) {
      Memory.writeByte(0x0100 + r_.S, r_.P);
      ++r_.S;
      return 3;
    },

    // Pull
    // accumulator
    'PLA': function(operands) {
      --r_.S;
      r_.A = Memory.readByte(0x0100 + r_.S);
      setFlag('ZERO', r_.A === 0);
      setFlag('NEGATIVE', (r_.A >> 7) === 1);
      return 4;
    },

    // Pull
    // status
    // register
    'PLP': function(operands) {
      --r_.S;
      r_.P = Memory.readByte(0x0100 + r_.S);
      return 4;
    },

    // LOGICAL
    // INSTRUCTIONS
    // Logical
    // AND
    // with
    // accumulator
    'AND': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      value = r_.A && value;
      r_.A = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;      
    },

    // EOR
    'EOR': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      value = r_.A ? !value : value;
      r_.A = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;
    },

    // ORA
    'ORA': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      value = r_.A || value;
      r_.A = value;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles;
    },


    // ARITHMETIC
    // INSTRUCTIONS
    // Add
    // with
    // carry
    'ADC': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.A + value;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);
      setFlag('OVERFLOW', (r_.A ^ value) & 0x80);

      if (addr_mode.mode == AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
    },

    // Decrement
    // with
    // carry
    'DEC': function(operands) {
      var addr_mode = getAddressingMode(operands);
      // TODO:
      // check
      // for
      // ZERO_PAGE_INDEX_X
      if (addr_mode.mode === AddressingMode.IMMEDIATE ||
          addr_mode.mode === AddressingMode.INDEX_X ||
          addr_mode.mode === AddressingMode.INDEX_Y) {
        throw 'Invalid addressing mode';
      }

      var value = Memory.readByte(addr_mode.address) - 1;
      Memory.writeByte(addr_mode.address, value);
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles + 2;
    },

    // Decrement
    // X
    'DEX': function(operands) {
      var value = r_.X - 1;
      r_.X = value & 0xFF;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return 2;
    },

    // Decrement
    // Y
    'DEY': function(operands) {
      var value = r_.Y - 1;
      r_.Y = value & 0xFF;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return 2;
    },

    // Increment
    'INC': function(operands) {
      var addr_mode = getAddressingMode(operands);
      // TODO:
      // check
      // for
      // ZERO_PAGE_INDEX_X
      if (addr_mode.mode === AddressingMode.IMMEDIATE ||
          addr_mode.mode === AddressingMode.INDEX_X ||
          addr_mode.mode === AddressingMode.INDEX_Y) {
        throw 'Invalid addressing mode';
      }

      var value = Memory.readByte(addr_mode.address) + 1;
      Memory.writeByte(addr_mode.address, value);
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return addr_mode.cycles + 1;
    },

    // Increment
    // X
    'INX': function(operands) {
      var value = r_.X + 1;
      r_.X = value & 0xFF;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return 2;
    },

    // Increment
    // Y
    'INY': function(operands) {
      var value = r_.Y + 1;
      r_.Y = value & 0xFF;
      setFlag('ZERO', value === 0);
      setFlag('NEGATIVE', (value >> 7) === 1);
      return 2;
    },

    // Subtract
    // with
    // carry
    'SBC': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.A - value;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);
      setFlag('OVERFLOW', (r_.A ^ value) & 0x80);

      if (addr_mode.mode == AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
      return addr_mode.cycles;
    },

    // BIT
    // MANIPULATION
    // OPERATIONS
    // Arithmetic
    // shift
    // left
    'ASL': function(operands) {
      var addr_mode = getAddressingMode(operands);

      if (addr_mode.mode === AddressingMode.INDEX_X || addr_mode.mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = value << 1;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);

      if (addr_mode.mode == AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
      return addr_mode.cycles + 2;
    },

    // Logical
    // shift
    // right
    'LSR': function(operands) {
      var addr_mode = getAddressingMode(operands);

      if (addr_mode.mode === AddressingMode.INDEX_X || addr_mode.mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = value >> 1;

      setFlag('ZERO', result === 0);
      setFlag('CARRY', (value & 0x1) == 1);

      if (addr_mode.mode === AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
      return addr_mode.cycles + 2;
    },

    // Rotate
    // left
    'ROL': function(operands) {
      var addr_mode = getAddressingMode(operands);

      if (addr_mode.mode === AddressingMode.INDEX_X || addr_mode.mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.A << value;
      result |= (r_.A >> 7);

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', (r_.A & 0x1) == 1);

      if (addr_mode.mode == AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
      return addr_mode.cycles + 2;
    },

    // Rotate
    // right
    'ROR': function(operands) {
      var addr_mode = getAddressingMode(operands);

      if (addr_mode.mode === AddressingMode.INDEX_X || addr_mode.mode === AddressingMode.INDEX_Y)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.A >> value;
      result |= (r_.A & 0x1) << 7;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', (r_.A & 0x1) == 1);

      if (addr_mode.mode == AddressingMode.IMMEDIATE)
        r_.A = (result & 0xFF);
      else
        Memory.writeByte(addr_mode.address, (result & 0xFF));
      return addr_mode.cycles + 2;
    },

    // SUBROUTINE
    // OPERATIONS
    // Jump
    'JMP': function(operands) {
      var addr_mode = getAddressingMode(operands);
      if (addr_mode.mode !== AddressingMode.ABSOLUTE && addr_mode.mode !== AddressingMode.INDEX)
        throw 'Invalid addressing mode';

      var value = (addr_mode.mode === AddressingMode.ABSOLUTE) ? addr_mode.address : Memory.readWord(addr_mode.address);
      r_.PC = value;
      // TODO:
      // jump
      // to
      // address
      // in
      // engine.
      return addr_mode.cycles - 1;
    },

    // Jump
    // subroutine
    'JSR': function(operands) {
      Memory.writeWord(0x0100 + r_.S, r_.PC);
      r_.S += 2;
      Memory.writeByte(0x0100 + r_.S, r_.P);
      ++r_.S;

      // TODO:
      // jump
      // to
      // address
      // in
      // engine.
      /*
         var addr_mode = getAddressingMode(operands);
         if (addr_mode.mode !== AddressingMode.ABSOLUTE)
         throw 'Invalid addressing mode';

         r_.PC = addr_mode.address;
       */
      Engine.setReturnHere();
      Engine.jumpToLabel(operands);
      return 6;
    },

    // Return
    // from
    // interrupt
    'RTI': function(operands) {
      --r_.S;
      r_.P = Memory.readByte(0x0100 + r_.S);
      r_.S -= 2;
      r_.PC = Memory.readWord(0x0100 + r_.S);

      // TODO:
      // get
      // the
      // name
      // of
      // the
      // label
      // from
      // engine
      Engine.jumpToLabel('return');

      return 6;
    },

    // Return
    // from
    // subroutine
    'RTS': function(operands) {
      --r_.S;
      r_.P = Memory.readByte(0x0100 + r_.S);
      r_.S -= 2;
      r_.PC = Memory.readWord(0x0100 + r_.S);

      // TODO:
      // Engine
      // hookup
      // to
      // PC
      // TODO:
      // Get
      // return
      // label
      // from
      // engine.
      Engine.jumpToLabel('return');

      return 6;
    },

    // COMPARISON
    // INSTRUCTIONS
    // Bit
    // test
    'BIT': function(operands) {
      var addr_mode = getAddressingMode(operands);
      if (addr_mode.mode !== AddressingMode.ABSOLUTE &&
          addr_mode.mode !== AddressingMode.ZEROPAGE) {
        throw 'Invalid addressing mode';
      }

      var value = Memory.readByte(addr_mode.address);
      var result = value & r_.A;
      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('OVERFLOW', (result >> 6) === 1);

      return addr_mode.cycles;
    },

    // Compare
    // with
    // accumulator
    'CMP': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.A - value;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);

      return addr_mode.cycles;
    },

    // Compare
    // with
    // X
    'CPX': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.X - value;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);

      return addr_mode.cycles;
    },

    // Compare
    // with
    // Y
    'CPY': function(operands) {
      var addr_mode = getAddressingMode(operands);
      var value = (addr_mode.mode == AddressingMode.IMMEDIATE) ? (addr_mode.value & 0xFF) : Memory.readByte(addr_mode.address);
      if (getFlag('DECIMAL'))
        value = toBCD(value);

      var result = r_.Y - value;

      setFlag('ZERO', result === 0);
      setFlag('NEGATIVE', (result >> 7) === 1);
      setFlag('CARRY', result > 0xFF);

      return addr_mode.cycles;
    },

    // BRANCHING
    // INSTRUCTIONS
    // TODO:
    // displacement
    // by
    // non-label
    // Branch
    // on
    // carry
    // clear
    'BCC': function(operands) {
      var cycles = 2;
      if (getFlag('CARRY') === 0) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // Branch
    // on
    // carry
    // set
    'BCS': function(operands) {
      var cycles = 2;
      if (getFlag('CARRY') === 1) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // Branch
    // on
    // equal
    'BEQ': function(operands) {
      var cycles = 2;
      if (getFlag('ZERO') === 1) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // Branch
    // on
    // minus
    'BMI': function(operands) {
      var cycles = 2;
      if (getFlag('NEGATIVE') === 1) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // Branch
    // on
    // not
    // equal
    'BNE': function(operands) {
      var cycles = 2;
      if (getFlag('ZERO') === 0) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // Branch
    // on
    // plus
    'BPL': function(operands) {
      var cycles = 2;
      if (getFlag('NEGATIVE') === 0) {
        // TODO:
        // cost
        // of
        // page
        // crossing.
        Engine.jumpToLabel(operands);
        ++cycles;
      }
      return cycles;
    },

    // STATUS
    // INSTRUCTIONS
    // Break
    'BRK': function(operands) {
      // TODO:
      // interrupt.
      Engine.pause();
      return 1;
    },

    // Clear
    // carry
    'CLC': function(operands) {
      setFlag('CARRY', false);
      return 2;
    },

    // Clear
    // decimal
    'CLD': function(operands) {
      setFlag('DECIMAL', false);
      return 2;
    },

    // Clear
    // interrupt
    'CLI': function(operands) {
      setFlag('INTERRUPT', false);
      return 2;
    },

    // Clear
    // overflow
    'CLV': function(operands) {
      setFlag('OVERFLOW', false);
      return 2;
    },

    // NOP
    'NOP': function(operands) {
      return 1;
    },

    // Set
    // carry
    'SEC': function(operands) {
      setFlag('CARRY', true);
      return 2;
    },

    // Set
    // decimal
    'SED': function(operands) {
      setFlag('DECIMAL', true);
      return 2;
    },

    // Set
    // interrupt
    'SEI': function(operands) {
      setFlag('INTERRUPT', true);
      return 2;
    }
  }
};

